/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package inode

import (
	reqContext "context"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/verifier"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	"github.com/spf13/cast"
	x509GM "github.com/tjfoc/gmsm/x509"
	"google.golang.org/grpc"
	"google.golang.org/grpc/keepalive"

	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/logging"
)

var logger = logging.NewLogger("ichainsdk/ichain")

// Inode inode节点
type Inode struct {
	config      ichain.EndpointConfig
	certificate *x509GM.Certificate
	serverName  string
	processor   ichain.ProposalProcessor
	iamID       string
	url         string
	kap         keepalive.ClientParameters
	failFast    bool
	inSecure    bool
	commManager ichain.CommManager
	properties  map[ichain.Property]interface{}
}

type Option func(*Inode) error

func New(config ichain.EndpointConfig, opts ...Option) (*Inode, error) {
	inode := &Inode{
		config:      config,
		commManager: &defCommManager{},
	}

	for _, opt := range opts {
		err := opt(inode)

		if err != nil {
			return nil, err
		}
	}

	if inode.processor == nil {
		endorseRequest := inodeEndorserRequest{
			target:             inode.url,
			certificate:        inode.certificate,
			serverHostOverride: inode.serverName,
			config:             inode.config,
			kap:                inode.kap,
			failFast:           inode.failFast,
			allowInsecure:      inode.inSecure,
			commManager:        inode.commManager,
		}
		processor, err := newInodeEndorser(&endorseRequest)

		if err != nil {
			return nil, err
		}
		inode.processor = processor
	}

	return inode, nil
}

func WithURL(url string) Option {
	return func(p *Inode) error {
		p.url = url

		return nil
	}
}

func WithTLSCert(certificate *x509GM.Certificate) Option {
	return func(p *Inode) error {
		p.certificate = certificate

		return nil
	}
}

func WithServerName(serverName string) Option {
	return func(p *Inode) error {
		p.serverName = serverName

		return nil
	}
}

func WithInsecure() Option {
	return func(p *Inode) error {
		p.inSecure = true

		return nil
	}
}

func WithIAMID(iamID string) Option {
	return func(p *Inode) error {
		p.iamID = iamID

		return nil
	}
}

func FromInodeConfig(inodeCfg *ichain.NetworkInode) Option {
	return func(p *Inode) error {

		p.url = inodeCfg.URL
		p.serverName = getServerNameOverride(inodeCfg)
		p.inSecure = isInsecureConnectionAllowed(inodeCfg)

		var err error
		p.certificate = inodeCfg.TLSCACert
		if inodeCfg.GRPCOptions["allow-insecure"] == false {
			//verify if certificate was expired or not yet valid
			err = verifier.ValidateCertificateDates(p.certificate)
			if err != nil {
				logger.Warn(err)
			}
		}

		p.iamID = inodeCfg.IAMID
		p.kap = getKeepAliveOptions(inodeCfg)
		p.failFast = getFailFast(inodeCfg)
		p.properties = inodeCfg.Properties

		return nil
	}
}

func getServerNameOverride(inodeCfg *ichain.NetworkInode) string {
	serverHostOverride := ""
	if str, ok := inodeCfg.GRPCOptions["ssl-target-name-override"].(string); ok {
		serverHostOverride = str
	}

	return serverHostOverride
}

func getFailFast(inodeCfg *ichain.NetworkInode) bool {
	var failFast = true
	if ff, ok := inodeCfg.GRPCOptions["fail-fast"].(bool); ok {
		failFast = cast.ToBool(ff)
	}

	return failFast
}

func getKeepAliveOptions(inodeCfg *ichain.NetworkInode) keepalive.ClientParameters {

	var kap keepalive.ClientParameters
	if kaTime, ok := inodeCfg.GRPCOptions["keep-alive-time"]; ok {
		kap.Time = cast.ToDuration(kaTime)
	}
	if kaTimeout, ok := inodeCfg.GRPCOptions["keep-alive-timeout"]; ok {
		kap.Timeout = cast.ToDuration(kaTimeout)
	}
	if kaPermit, ok := inodeCfg.GRPCOptions["keep-alive-permit"]; ok {
		kap.PermitWithoutStream = cast.ToBool(kaPermit)
	}
	return kap
}

func isInsecureConnectionAllowed(inodeCfg *ichain.NetworkInode) bool {
	allowInsecure, ok := inodeCfg.GRPCOptions["allow-insecure"].(bool)
	if ok {
		return allowInsecure
	}
	return false
}

func WithInodeProcessor(processor ichain.ProposalProcessor) Option {
	return func(p *Inode) error {
		p.processor = processor

		return nil
	}
}

func (p *Inode) IAMID() string {
	return p.iamID
}

func (p *Inode) URL() string {
	return p.url
}

func (p *Inode) ProcessTransactionProposal(ctx reqContext.Context, proposal ichain.ProcessProposalRequest) (*ichain.TransactionProposalResponse, error) {
	return p.processor.ProcessTransactionProposal(ctx, proposal)
}

func (p *Inode) Properties() ichain.Properties {
	return p.properties
}

func (p *Inode) String() string {
	return p.url
}

type defCommManager struct{}

func (*defCommManager) DialContext(ctx reqContext.Context, target string, opts ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
	logger.Debugf("DialContext [%s]", target)
	opts = append(opts, grpc.WithBlock())
	return grpc.DialContext(ctx, target, opts...)
}

func (*defCommManager) ReleaseConn(conn *grpc.ClientConn) {
	logger.Debugf("ReleaseConn [%p]", conn)
	if err := conn.Close(); err != nil {
		logger.Debugf("unable to close connection [%s]", err)
	}
}
